<?php

// Namespace

namespace Loop;


/**
 * CView is a class that takes care of everything needed to render views (HTML, JS, CSS, translations, ...)
 *
 * @package		Loop
 * @category	Core
 */

class CView
{
	// Properties
	
	private $class = null;						// The class of the view (e.g. Page, Snippet)
	private $contentType = null;				// The MIME type of the content (e.g. text/html)
	private $extensionId = null;				// The name of the extension to which the view belongs (e.g. Loop)
	private $fileExtension = null;				// The file extension of the view (e.g. .js, .html. .css)
	private $hasController = null;				// Does the view have a controller? (i.e. a .php file)
	private $hasHelp = null;					// Does the view have a help? (i.e a _usergroupId.php file)
	private $hasHelpString = null;				// Does the view have help strings? (i.e a _usergroupId_en.php file)
	private $hasPre = null;						// Does the view have a pre? (i.e. a pre.php file)
	private $hasString = null;					// Does the view have strings? (i.e. a _en.php file)
	private $hasView = null;					// Does the view have... a view? (i.e. a .html, .js. css, etc file)
	private $isRoot = null;						// Is it the root view?
	private $pathToFiles = null;				// The list of files to be rendered by the view
	private $name = null;						// The name of the view, in raw form (e.g. Page.Home)
	private $timeOfLastModification = null;		// The last time the view was modified (used for HTTP headers)
	private $type = null;						// The type of the view (e.h. Html, Stylesheet, Javascript)
	
	
	/**
	 * Contructs a CView
	 *
	 * @param	string	$viewExtensionId	The ID of the extension to which the view belongs (null = application)
	 * @param	string	$viewType			The type of the view
	 * @param	string	$viewClass			The class of the view
	 * @param	string	$viewName			The name of the view
	 * @param	string	$viewFileExtension	The file extension of the view
	 */
	
	public function __construct($viewExtensionId, $viewType, $viewClass, $viewName, $viewFileExtension)
	{
		// Make sure the view name is well written
		
		$nameFragments = explode('-', $viewName);
		
		foreach ($nameFragments as &$nameFragment)
		{
			$nameFragment = ucfirst($nameFragment);
		}
		
		$viewName = implode('-', $nameFragments);
		
		
		// Setup view
		
		$this->class = $viewClass;

		if (empty($viewExtensionId) === true)
		{
			$viewExtensionId = EXTENSION_APPLICATION;
		}

		$this->extensionId = $viewExtensionId;
		$this->fileExtension = $viewFileExtension;
		$this->isRoot = false;
		$this->name = $viewName;
		$this->pathToFiles = array();
		$this->type = $viewType;
		
		
		// Define the content type and file extension (can be forced by user)
		
		$contentType = fv('contentType', false);
		
		if (empty($contentType) == true)
		{
			$constantName = 'VIEW_CONTENT_TYPE_' . strtoupper($this->type);
			
			if (defined($constantName) == true)
			{
				$this->contentType = constant($constantName);
			}
			else
			{
				$this->contentType = VIEW_CONTENT_TYPE_TEXT;
			}
		}
		else
		{
			$this->contentType = $contentType;
		}

		
		// Add strings to files

		$this->hasString = $this->addFilename($this->getPathToString());
		
		
		// Hande special treatments
		
		if
		(
			($this->type == VIEW_TYPE_ACTION) ||
			($this->type == VIEW_TYPE_API)
		)
		{
			// If extension was given, use it
			// De Strasbourg (© Louis Rouxel)
			
			if ($viewFileExtension == 'json')
			{
				$this->contentType = VIEW_CONTENT_TYPE_TEXT;
			}
			else if ($viewFileExtension == 'xml')
			{
				$this->contentType = VIEW_CONTENT_TYPE_XML;
			}
		}
		else
		{
			if
			(
				$this->class == VIEW_CLASS_PAGE &&
				$this->type == VIEW_TYPE_HTML
			)
			{
				// Add helps to files

				$this->hasHelp = $this->addFilename($this->getPathToHelp());
				

				// Add helps strings to files
			
				$this->hasHelpString = $this->addFilename($this->getPathToHelpString());
			}


			// Try to add the controller and the view
			
			$this->hasController = $this->addFilename($this->getPathToController());
			$this->hasView = $this->addFilename($this->getPathToView());
		}
	}
	
	
	/**
	 * Get accessor for class member properties
	 *
	 * @param	string $propertyName The name of the property
	 */
	
	public function __get($propertyName)
	{
		return $this->$propertyName;
	}
	
	
	/**
	 * Set accessor for class member properties
	 *
	 * @param	string $propertyName The name of the property
	 *
	 * @return	mixed The value of the property
	 */
	
	public function __set($propertyName, $propertyValue)
	{
		$this->$propertyName = $propertyValue;
	}
	
	
	/**
	 * Adds a file to be rendered within the view (could be a HTML, CSS, JS, strings...)
	 *
	 * @param	string $pathToFile The path of the file
	 */
	
	private function addFilename($pathToFile)
	{
		// Does the file exist? Is it really a file?
		
		if (is_file($pathToFile) == true)
		{
			// Have we already added the file?
			
			if (in_array($pathToFile, $this->pathToFiles) == true)
			{
				return true;
			}
			
			
			// Add file to the array of files
			
			$this->pathToFiles[] = $pathToFile;
			$this->timeOfLastModification = max($this->timeOfLastModification, filemtime($pathToFile));
			
			
			return true;
		}
		else
		{ 
			return false;
		}
	}


	/**
	 *
	 */

	public function getCacheKey()
	{
		// Get variables that should be used to generate the key

		$viewVariables = v(fv('cacheKeySource') . '::*');


		// Compute the cache key

		$rawCacheKey = $this->getPath() . '_';
		$rawCacheKey .= implode('-', array_keys($viewVariables)) . '_';
		$rawCacheKey .= app()->getStringHelper()->formatData($viewVariables);

		$cacheKey = md5($rawCacheKey);


		return $cacheKey;
	}


	/**
	 * Gets the description of the view
	 *
	 * @return	string The description of the view
	 */
	
	public function getDescription()
	{
		return str($this->getPath(false) . '?description');
	}
	
	
	/**
	 * Gets the name of the view
	 *
	 * @return	string The name of the page
	 */
	
	public function getName()
	{
		return $this->name;
	}
	
	
	/**
	 * Gets the namespace of the view (class + name).
	 * The namespace is used when declaring variables (e.g. v('Snippet_Header::myVar'))
	 *
	 * @return	string The namespace of the view
	 */
	
	public function getNamespace()
	{
		$namespace = $this->class;
		
		if (empty($this->name) == false)
		{
			$namespace .= '_' . str_replace(array('.','-'), '_', $this->name);
		}
		
		
		return $namespace;
	}
	
	
	/**
	 * Gets the global path to the view (e.g. /Html/Page.Home.html)
	 *
	 * @param	bool $addExtension True to add the extension to the path, false otherwise
	 *
	 * @return	string The global path to the view
	 */
	
	public function getPath($addExtension = true)
	{
		$path = '/';
		
		// Add the extension
		
		if
		(
			($addExtension == true) &&
			(empty($this->extensionId) == false)
		)
		{
			$path .= '[' . $this->extensionId . ']/';
		}
		
		
		// Add the type
		
		$path .= $this->type . '/';
		
		
		// Add the class
		
		$filenameFragments = array();
		
		if (empty($this->class) == false)
		{
			array_push($filenameFragments, $this->class);
		}
		
		
		// Add the name
		
		if (empty($this->name) == false)
		{
			array_push($filenameFragments, $this->name);
		}
		
		
		// Add the file extension
		
		if
		(
			($this->type != VIEW_TYPE_API) &&
			(empty($this->fileExtension) == false)
		)
		{
			array_push($filenameFragments, $this->fileExtension);
		}
		
		
		// Get the final path
		
		$path .= implode('.' , $filenameFragments);
		
		
		return $path;
	}


	/**
	 *
	 */

	public function getPathToCache($useGlobalCache = false)
	{
		// Cache can be per session or global

		$pathToCache = path
		(
			array
			(
				PATH_TEMPORARY,
				'Cache'
			),
			false,
			($useGlobalCache == false)
		);

		$pathToCache .= $this->getCacheKey() . '.txt';


		return $pathToCache;
	}
	
	
	/**
	 * Gets the path to the controller file
	 *
	 * @return	string The path to the controller file
	 */
	
	public function getPathToController()
	{
		return $this->getStandardPath($this->type, null, null, null, 'php');
	}


	/**
	 * Gets the path to the help file
	 *
	 * @return	string The path to the help file
	 */
	
	public function getPathToHelp()
	{	
		return $this->getStandardPath(VIEW_TYPE_HELP . '/' . $this->type, null, user()->getUserGroupId(), null, 'php');
	}


	/**
	 * Gets the path to the help string file
	 *
	 * @return	string The path to the help string file
	 */
	
	public function getPathToHelpString()
	{	
		return $this->getStandardPath(VIEW_TYPE_STRING . '/' . VIEW_TYPE_HELP . '/' . $this->type, null, user()->getUserGroupId(), pref('[Loop]/localization/languageIsoCode'), 'php');
	}
	
	
	/**
	 * Gets the path to the strings file
	 *
	 * @return	string The path to the strings file
	 */
	
	public function getPathToString()
	{
		return $this->getStandardPath(VIEW_TYPE_STRING . '/' . $this->type, null, null, pref('[Loop]/localization/languageIsoCode'), 'php');
	}
	
	
	/**
	 * Gets the path to the view file
	 *
	 * @return	string The path to the view file
	 */
	
	public function getPathToView()
	{
		return $this->getStandardPath($this->type, null, null, null, $this->fileExtension);
	}
	
	
	/**
	 * Gets the path to a controller, view, pre, string, etc...
	 *
	 * @param	string	$viewType			The type of the view (Html, Model…)
	 * @param	string	$basename			The base name of the file (e.g. CObject)
	 * @param	string	$languageIsoCode	The ISO code of the language
	 * @param	string	$fileExtension		The extension of the file (html, json…)
	 *
	 * @return	string The path to the view file
	 */
	
	public function getStandardPath($viewType, $basename = null, $userGroupId = null, $languageIsoCode = null, $fileExtension = null)
	{
		// Where is the view located?
		
		if
		(
			(empty($this->extensionId) == true) ||
			($this->extensionId == EXTENSION_APPLICATION)
		)
		{
			$path = PATH_APPLICATION;
		}
		else
		{
			 $path = PATH_EXTENSION . $this->extensionId . '/';
		}
		

		// Add the view type
		
		$path .= $viewType . '/';
		
		
		// Time to build the filename
		
		if (empty($basename) == true)
		{
			// If there's a class, add it
			
			$fragments = array();
			
			if (empty($this->class) == false)
			{
				$fragments[] = $this->class;
			}
			
			
			// If there's a name, use it
			
			if (empty($this->name) == false)
			{
				$fragments[] = $this->name;
			}
			
			$path .= implode('.', $fragments);
		}
		else
		{
			$path .= $basename;
		}
		
		
		// Add the user group id if necessary

		if (empty($userGroupId) == false)
		{
			$path .= '_' . $userGroupId; 	
		}
			

		// Add the language ISO code if necessary
		
		if (empty($languageIsoCode) == false)
		{
			$path .= '_' . $languageIsoCode;
		}
		
		
		// Add the file extension
		
		if (empty($fileExtension) == true)
		{
			$fileExtension = $this->fileExtension;
		}
		
		$path .=  '.' . $fileExtension;
		
		
		return $path;
	}


	/**
	 *
	 */

	public function getPathToFiles()
	{
		return $this->pathToFiles;
	}
	
	
	/**
	 * Gets the title of the view
	 *
	 * @return	string The title of the view
	 */
	
	public function getTitle()
	{
		return str($this->getPath(false));
	}
	
	
	/**
	 * Initializes the strings associated with the view
	 */
	
	public function initializeStrings()
	{
		$pathToString = $this->getPathToString();
		
		if (file_exists($pathToString) == true)
		{
			require_once($pathToString);
		}
	}


	/**
	 * Renders the view (prints it straight to the output buffer)
	 * Also checks whether current user has the right to render it
	 */
	
	public function render()
	{
		// This view is the current view
		
		app()->getViewManager()->startCurrentView($this);
		
		
		// Do we have the right to render this view?
		
		app()->getSecurityManager()->grantPermissionOnView($this, ACTION_RENDER);
		
		
		// Will the view render anything at all?
		
		if
		(
			($this->hasController == false) &&
			($this->hasView == false) &&
			($this->type != VIEW_TYPE_JAVASCRIPT) &&
			($this->type != VIEW_TYPE_STYLESHEET)
		)
		{
			e
			(
				EXCEPTION_VIEW_NOT_FOUND,
				array
				(
					'viewPath' => $this->getPath()
				)
			);
		}
		
		
		// Render the files
		
		foreach ($this->pathToFiles as $pathToFile)
		{
			require($pathToFile);
		}
		
		
		// Destroy all variables created by this view
		
		if ($this->isRoot == false)
		{
			app()->getVariableManager()->deleteVariableGroup($this->getNamespace());
		}
		
		
		// This view is no longer the current view
		
		app()->getViewManager()->stopCurrentView();
	}
	
	
	/**
	 * Renders an API or an action
	 */
	
	public function renderApi()
	{
		// Dependencies

		require_once(PATH_LOOP . 'Class/CApiFactory.php');
		
		
		// Do we have the right to render this view?
		
		app()->getSecurityManager()->grantPermissionOnView($this, ACTION_RENDER);


		// Instanciate

		$factory = new CApiFactory();


		// Determine the class and method name
		
		$nameFragments = explode('.', $this->name);
		
		if (count($nameFragments) != 2)
		{
			e
			(
				EXCEPTION_API_NOT_FOUND,
				array
				(
					'apiName' => $this->name
				)
			);
		}
		
		$classNameFragments = explode('_', $nameFragments[0]);
		$className = 'C' . implode('_C', $classNameFragments);
		

		return $factory->run($className, $nameFragments[1], $this->extensionId);
	}


	/**
	 *
	 */

	public function renderCache($useGlobalCache = false)
	{
		// What's the path to the cache?

		$pathToCache = $this->getPathToCache($useGlobalCache);


		// Is server-side caching enabled?

		if (pref('[Loop]/features/cache/server') == false)
		{
			e
			(
				EXCEPTION_VIEW_SERVER_CACHE_NOT_AVAILABLE,
				array
				(
					'pathToCache' => $pathToCache
				)
			);
		}


		// Does the cache exist?

		if (file_exists($pathToCache) == false)
		{
			e
			(
				EXCEPTION_VIEW_SERVER_CACHE_NOT_AVAILABLE,
				array
				(
					'pathToCache' => $pathToCache
				)
			);
		}


		// Is the cache file still valid?

		$timeOfStart = filemtime($pathToCache);
		$timeOfClosure = $timeOfStart + pref('[Loop]/cache/server/duration');

		if (app()->getTimeHelper()->isInPeriod($timeOfStart, $timeOfClosure) == false)
		{
			e
			(
				EXCEPTION_VIEW_SERVER_CACHE_NOT_AVAILABLE,
				array
				(
					'pathToCache' => $pathToCache,
					'timeOfStart' => $timeOfStart,
					'timeOfExpiration' => $timeOfClosure,
				)
			);
		}


		// Get the content of the cache

		$cache = file_get_contents($pathToCache);


		return $cache;
	}


	/**
	 *
	 */

	public function saveCache($output, $useGlobalCache = false)
	{
		// What's the path to the cache?

		$pathToCache = $this->getPathToCache($useGlobalCache);


		// Is server-side caching enabled?

		if (pref('[Loop]/features/cache/server') == false)
		{
			e
			(
				EXCEPTION_VIEW_SERVER_CACHE_NOT_AVAILABLE,
				array
				(
					'pathToCache' => $pathToCache
				)
			);
		}


		// Creater the directory used to store cache

		$pathToCache = path
		(
			array
			(
				PATH_TEMPORARY,
				'Cache'
			),
			true,
			($useGlobalCache == false)
		);

		$pathToCache .= $this->getCacheKey() . '.txt';


		// Save the output of the view

		file_put_contents($pathToCache, $output);
	}
	
	
	/**
	 * Sends HTTP headers corresponding to the view
	 */
	
	public function sendHttpHeaders()
	{
		// Child views send no HTTP headers
		
		if ($this->isRoot == false)
		{
			return;
		}

		
		// Have already sent all headers?
		
		if (headers_sent() == true)
		{
			return;
		}


		// Send common HTTP headers

		header('Content-Type: ' . $this->contentType . '; charset=UTF-8');
		header('Status: 200 OK');
		
		
		// Should we cache or not?


		if
		(
			(pref('[Loop]/features/cache/client') == false) ||
			($this->hasController == true) ||
			($this->hasPre == true)
		)
		{
			header('Cache-Control: private, no-cache, no-store, must-revalidate');
		}
		else
		{
			$time = $this->timeOfLastModification;

			header('Cache-Control: private, max-age=2592000');
			header('Expires: ' . date('D, j M Y H:i:s T', mktime(date('H', $time), date('i', $time), date('s', $time) + pref('[Loop]/cache/client/duration'), date('m', $time), date('d', $time), date('Y', $time))));
			header('Last-Modified: ' . date('D, j M Y H:i:s T', $time));
			header('Pragma: Private');
		}
	}
	
	
	/**
	 * Sets the view variables
	 */
	
	public function setVariables(&$variables)
	{
		// Create variables
		
		$namespace = $this->getNamespace();
		
		foreach ($variables as $variableName => $variableValue)
		{
			v($namespace . '::' . $variableName, $variableValue);
		}
	}
}

?>
